import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

import javax.swing.*
import java.awt.Dialog
import java.lang.reflect.Method
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.stream.Collectors
import java.lang.String


config = [
    // 生成开关
    generate: [
        entity          : true,
        service         : true,
        repository      : true,
        repositoryCustom: true
    ],
    // 实体生成设置
    entity  : [
        // 继承父类设置
        parent         : [
            // 是否继承父类
            enable    : true,
            // 父类名称
            name      : "BaseEntity",
            // 父类包名
            package   : "com.winning.dps.df.frame.common.core.domain",
            // 父类的属性，父类已有属性不在出现在生成的实体内
            properties: ["createBy", "createTime", "updateBy", "updateTime", "remark", "isDel", "isEnable"],
        ],
        // 是否序列化
        impSerializable: true,
        // 是否生成 jpa 相关内容，设置为 false 可以生成与 jpa 无关的实体
        jpa            : true,
        // 是否生成 swagger 文档相关注解，相关说明来数据库注释
        useSwagger     : true,
        // 是否使用 lombok 注解代替 get、set方法
        useLombok      : true
    ],
    // service 生成设置
    service : [
        // 参照 entity 部分的 parent
        parent: [
            enable : true,
            name   : "BaseService",
            package: "com.winning.dps.df.frame.service"
        ]
    ]
]

typeMapping = [
    (~/(?i)bool|boolean|tinyint/)     : "Boolean",
    (~/(?i)bigint/)                   : "Long",
    (~/(?i)int/)                      : "Integer",
    (~/(?i)float|double|decimal|real/): "Double",
    (~/(?i)datetime|timestamp/)       : "java.util.Date",
    (~/(?i)date/)                     : "java.sql.Date",
    (~/(?i)time/)                     : "java.sql.Time",
    (~/(?i)/)                         : "String"
]


FILES.chooseDirectoryAndSave("\u9009\u62e9\u6587\u4ef6\u5939", "\u9009\u62e9\u6587\u4ef6\u751f\u6210\u4f4d\u7f6e") { dir ->
    SELECTION.filter {
        it instanceof DasTable && it.getKind() == ObjectKind.TABLE
    }
        .each { table ->
            def fields = calcFields(table)
            Gen.main(config, table, fields, dir.toString())
        }
}

// 转换类型
def calcFields(table) {
    def pk = Utils.getPK(table)
    DasUtil.getColumns(table).reduce([]) { fields, col ->
//        console(col, i++)
        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
        fields += [[
                       name        : Utils.javaName(col.getName(), false),
                       column      : col.getName(),
                       type        : typeStr,
                       dataType    : Utils.firstMatched(col.getDataType(), /\b\w+\b/, ""),
                       len         : Utils.firstMatched(col.getDataType(), /(?<=\()\d+(?!=\))/, -1),
                       default     : col.getDefault(),
                       comment     : col.getComment(),
                       nullable    : !col.isNotNull(),
                       isPrimaryKey: null != pk && pk == col.getName(),
                   ]]
    }
}



class Gen {

    // 生成对应的文件
    def static main(config, table, fields, dir) {
        def entityName = Utils.javaName(table.getName(), true)
        def basePackage = Utils.firstMatched(dir.toString(), /(?<=src\\main\\java\\).+/, "").replace("\\", ".")
        dir = dir.toString()
        def pkType = fields.find { it.isPrimaryKey }.type
        // entity
        if (config.generate.entity) {
            Utils.createFile("${dir}\\domain", "${entityName}.java").withWriter("utf8") {
                writer -> genEntity(writer, config, config.entity.parent, table, entityName, fields, basePackage)
            }
        }
        // service
        if (config.generate.service) {
            Utils.createFile("${dir}\\service", "${entityName}Service.java").withWriter("utf8") {
                writer -> genService(writer, config, config.service.parent, entityName, pkType, basePackage)
            }
            Utils.createFile("${dir}\\service\\impl", "${entityName}ServiceImpl.java").withWriter("utf8") {
                writer -> genServiceImpl(writer, config, config.service.parent, entityName, pkType, basePackage)
            }
        }

        // rep
        if (config.generate.repository) {
            Utils.createFile("${dir}\\repositories", "${entityName}Repository.java").withWriter("utf8") {
                writer -> genRepository(writer, config, entityName, basePackage, pkType)
            }
        }

        //生成api
        Utils.createFile("${dir}\\api", "${entityName}RpcService.java").withWriter("utf8") {
            writer -> genApi(writer,entityName)
        }

        //生成rest
        Utils.createFile("${dir}\\rest", "${entityName}EndPointImpl.java").withWriter("utf8") {
            writer -> genRest(writer,entityName)
        }
    }

    // 生成实体
    def static genEntity(writer, config, parentConfig, table, entityName, fieldList, basePackage) {

        //writer.writeLine "package ${basePackage}.entity;"
        writer.writeLine ""
        if (config.entity.useSwagger) {
            writer.writeLine "import io.swagger.annotations.ApiModel;"
            writer.writeLine "import io.swagger.annotations.ApiModelProperty;"
        }
        writer.writeLine "import com.winning.dps.df.frame.common.core.domain.BaseEntity;"
        writer.writeLine "import org.hibernate.annotations.GenericGenerator;"
        writer.writeLine "import lombok.experimental.Accessors;"
        writer.writeLine "import org.hibernate.annotations.DynamicInsert;"
        writer.writeLine "import org.hibernate.annotations.DynamicUpdate;"

        if (config.entity.jpa) {
            writer.writeLine "import javax.persistence.*;"
        }
        if (config.entity.useLombok) {
            writer.writeLine "import lombok.Data;"
            writer.writeLine ""
        }

        def tableComment = Utils.getDefaultValIfCurrentValIsBlank(table.getComment(), entityName)
        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $tableComment"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"
        if (config.entity.useLombok) {
            writer.writeLine "@Data"
        }
        if (config.entity.jpa) {
            writer.writeLine "@Entity"
            writer.writeLine "@Table(name = \"${table.name}\")"
            writer.writeLine "@Accessors(chain = true)"
            writer.writeLine "@DynamicInsert"
            writer.writeLine "@DynamicUpdate"
        }
        if (config.entity.useSwagger) {
            writer.writeLine "@ApiModel(value = \"${tableComment}\")"
        }

        def extendsStr = parentConfig.enable ? " extends $parentConfig.name" : ""

        writer.writeLine "public class $entityName$extendsStr {"

        if (parentConfig.enable) {
            fieldList = fieldList.findAll { field -> !parentConfig.properties.contains(field.name) }
        }

        fieldList.each() { field -> genEntityProperties(writer, config, parentConfig, field) }

        if (!config.entity.useLombok) {
            fieldList.each() { field -> genEntityGetAndSetMethod(writer, field) }
        }
        writer.writeLine "}"
    }

    // 实体属性
    def static genEntityProperties(writer, config, parentConfig, field) {
        writer.writeLine ""
        def comment = Utils.getDefaultValIfCurrentValIsBlank(field.comment, field.name)
        writer.writeLine "\t/**"
        writer.writeLine "\t * ${comment}"
        writer.writeLine "\t * nullable : ${field.nullable}"
        writer.writeLine "\t * default  : ${field.default}"
        writer.writeLine "\t */"

        if (field.isPrimaryKey && config.entity.jpa) {
            writer.writeLine "\t@Id"
//            writer.writeLine "\t@GenericGenerator(name = \"system-uuid\", strategy = \"uuid\")"
//            writer.writeLine "\t@GeneratedValue(generator = \"system-uuid\")"
        }
        if (config.entity.useSwagger) {
            writer.writeLine "\t@ApiModelProperty(value = \"${comment}\")"
        }

        if (config.entity.jpa) {
            def lenStr = ""
            if (field.len.toInteger() >= 0 && !field.type.contains("java")) {
                lenStr = ", length = $field.len"
            }
            writer.writeLine "\t@Column(name = \"${field.column}\", nullable = ${!field.isNotNull}$lenStr)"
        }
        writer.writeLine "\tprivate ${field.type} ${field.name};"
    }

    // 生成Service
    def static genService(writer, config, parentConfig, entityName, pkType, basePackage) {
        //writer.writeLine "package ${basePackage}.service;"
        writer.writeLine ""
        writer.writeLine "import com.winning.dps.df.frame.service.BaseService;"

        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $entityName service\u5c42"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"

        def extendsStr = parentConfig.enable ? " extends ${parentConfig.name}<$entityName>" : ""
        writer.writeLine "public interface ${entityName}Service${extendsStr} {"
        writer.writeLine ""

        writer.writeLine "}"
    }

    // genServiceImpl
    def static genServiceImpl(writer, config, parentConfig, entityName, pkType, basePackage) {
        //writer.writeLine "package ${basePackage}.service.impl;"
        writer.writeLine ""

        def impl = "impl"
        writer.writeLine "import com.winning.dps.df.frame.service.BaseServiceImpl;"
        //writer.writeLine "import ${basePackage}.domain.$entityName;"

        //writer.writeLine "import ${basePackage}.repository.${entityName}Repository;"
        writer.writeLine "import org.springframework.stereotype.Service;"
        writer.writeLine "import org.springframework.beans.factory.annotation.Autowired;"

        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $entityName service\u5c42"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"

        writer.writeLine "@Service"
        def extendsStr = parentConfig.enable ? " extends BaseServiceImpl<${entityName}Repository, $entityName> " +
            "implements ${entityName}Service" : ""
        writer.writeLine "public class ${entityName}ServiceImpl${extendsStr} {"
        writer.writeLine ""

        writer.writeLine "  @Autowired"
        writer.writeLine "  private ${entityName}Repository repository;"
        writer.writeLine ""
        writer.writeLine "}"
    }

    // 生成rep
    def static genRepository(writer, config, entityName, basePackage, pkType) {

        //writer.writeLine "package ${basePackage}.repository;"
        writer.writeLine ""
        //writer.writeLine "import ${basePackage}.entity.$entityName;"
        writer.writeLine "import com.winning.dps.df.frame.repositories.BaseRepository;"
        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $entityName Repository\u5c42"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"
        writer.writeLine "public interface ${entityName}Repository extends BaseRepository<$entityName> {"
        writer.writeLine ""
        writer.writeLine "}"
    }

    // 生成Api
    def static genApi(writer, entityName) {
        //writer.writeLine "package ${basePackage}.service;"
        writer.writeLine ""
        //writer.writeLine "import ${basePackage}.domain.$entityName;"
        writer.writeLine "import com.winning.base.akso.rpc.WinRpcResponse;"
        writer.writeLine "import com.winning.base.akso.tie.ApiTie;"
        writer.writeLine "import com.winning.base.akso.tie.Domain;"
        writer.writeLine "import com.winning.base.akso.tie.Ties;"
        writer.writeLine "import com.winning.dps.df.frame.constant.V1ApiPathConst;"
        writer.writeLine "import org.springframework.web.bind.annotation.PostMapping;"

        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $entityName rpcService\u5c42"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"

        writer.writeLine ""
        writer.writeLine "@ApiTie(value = Ties.BMTS, domain = Domain.DMTS)"
        writer.writeLine "public interface ${entityName}RpcService {"
        writer.writeLine ""

        writer.writeLine "}"
    }

    // 生成Rest
    def static genRest(writer, entityName) {

        def lowerName = Utils.getLowerEntityName(entityName)

        writer.writeLine "import cn.hutool.core.util.ArrayUtil;"
        writer.writeLine "import com.winning.base.akso.mvc.WinMvcResponse;"
        writer.writeLine "import com.winning.base.akso.swagger.info.ApiStatus;"
        writer.writeLine "import com.winning.base.akso.swagger.info.WinningApiInfo;"
        writer.writeLine "import com.winning.dps.df.frame.common.base.PageParams;"
        writer.writeLine "import com.winning.dps.df.frame.common.enums.DelFlag;"
        writer.writeLine "import com.winning.dps.df.frame.common.path.ApiPath;"
        writer.writeLine "import com.winning.dps.df.frame.common.utils.poi.DateUtils;"
        writer.writeLine "import com.winning.dps.df.frame.rest.base.BaseEndPoint;"
        writer.writeLine "import io.swagger.annotations.Api;"
        writer.writeLine "import io.swagger.annotations.ApiOperation;"
        writer.writeLine "import io.swagger.annotations.ApiParam;"
        writer.writeLine "import org.springframework.beans.factory.annotation.Autowired;"
        writer.writeLine "import org.springframework.data.domain.Page;"
        writer.writeLine "import org.springframework.validation.annotation.Validated;"
        writer.writeLine "import org.springframework.web.bind.annotation.*;"
        writer.writeLine "import org.springframework.data.domain.Example;"
        writer.writeLine "import java.util.Arrays;"

        writer.writeLine ""
        writer.writeLine "/**"
        writer.writeLine " * $entityName rest\u5c42"
        writer.writeLine " *"
        writer.writeLine " * @author auto generated"
        writer.writeLine " * @date ${Utils.localDateTimeStr()}"
        writer.writeLine " */"

        def tagName = "DataFactory"
        def apiInfo = "Dict"
        def extendsStr =  " extends BaseEndPoint implements ${entityName}RpcService"
        writer.writeLine "@Api(tags = \"${tagName}\")"
        writer.writeLine "@RequestMapping(ApiPath.ROOT + \"/${lowerName}\")"
        writer.writeLine "@RestController"
        writer.writeLine "public class ${entityName}EndPointImpl${extendsStr} {"
        writer.writeLine ""
        writer.writeLine "\t@Autowired"
        def service =lowerName + "Service"
        writer.writeLine "\t${entityName}Service ${service};"
        writer.writeLine ""
        writer.writeLine "\t@WinningApiInfo(id = \"${apiInfo}\",apiVersion = \"v1\", sinceVersion = \"1.0.0\", createdAt = \"${Utils.localDateTime()}\", updatedAt = \"${Utils.localDateTime()}\", apiStatus = ApiStatus.DONE)"
        writer.writeLine "\t@ApiOperation(value = \"\u5206\u9875\u67e5\u8be2\", httpMethod = \"POST\")"
        writer.writeLine "\t@PostMapping(path = \"/list\")"
        writer.writeLine "\tpublic WinMvcResponse pageList(@ApiParam(required = true) @RequestBody PageParams<${entityName}> pageParams) {"
        writer.writeLine "\t    pageParams.getQueryParams().put(GlobalContants.QUERY_PARAM_ISDEL, DelFlag.OK.getCode());"
        writer.writeLine "\t    Page<${entityName}> pageList = ${service}.customPageList(pageParams.buildSpec(),pageParams.buildPage());"
        writer.writeLine "\t    return pageResult(pageList.getContent(), pageList.getTotalElements());"
        writer.writeLine "\t}"
        writer.writeLine ""
        writer.writeLine "\t@WinningApiInfo(id = \"${apiInfo}\",apiVersion = \"v1\", sinceVersion = \"1.0.0\", createdAt = \"${Utils.localDateTime()}\", updatedAt = \"${Utils.localDateTime()}\", apiStatus = ApiStatus.DONE)"
        writer.writeLine "\t@ApiOperation(value = \"\u4fdd\u5b58\", httpMethod = \"POST\")"
        writer.writeLine "\t@PostMapping(path = \"/save\")"
        writer.writeLine "\tpublic WinMvcResponse save(@RequestBody @Validated ${entityName} entity) {"
        writer.writeLine "\t    ${service}.customSaveOrUpdate(entity,entity.get${entityName}Id());"
        writer.writeLine "\t    return successResult();"
        writer.writeLine "\t}"
        writer.writeLine ""
        writer.writeLine "\t@WinningApiInfo(id = \"${apiInfo}\",apiVersion = \"v1\", sinceVersion = \"1.0.0\", createdAt = \"${Utils.localDateTime()}\", updatedAt = \"${Utils.localDateTime()}\", apiStatus = ApiStatus.DONE)"
        writer.writeLine "\t@ApiOperation(value = \"\u83b7\u53d6\u5355\u4e2a\u4fe1\u606f\", httpMethod = \"GET\")"
        writer.writeLine "\t@GetMapping(path = \"/get/{id}\")"
        writer.writeLine "\t public WinMvcResponse getById(@PathVariable Integer id) {"
        writer.writeLine "\t${entityName} exampleEntity = new ${entityName}().set${entityName}Id(id);"
        writer.writeLine "\texampleEntity.setIsDel(DelFlag.OK.getCode());"
        writer.writeLine "\tExample<${entityName}> example = Example.of(exampleEntity);"
        writer.writeLine "\treturn successResult(${service}.getOneByExample(example));"
        writer.writeLine "\t}"
        writer.writeLine ""
        writer.writeLine "\t@WinningApiInfo(id = \"${apiInfo}\",apiVersion = \"v1\", sinceVersion = \"1.0.0\", createdAt = \"${Utils.localDateTime()}\", updatedAt = \"${Utils.localDateTime()}\", apiStatus = ApiStatus.DONE)"
        writer.writeLine "\t@ApiOperation(value = \"\u5220\u9664\", httpMethod = \"POST\")"
        writer.writeLine "\t@PostMapping(path = \"/delete/{ids}\")"
        writer.writeLine "\tpublic WinMvcResponse delete(@PathVariable Integer[] ids) {"
        writer.writeLine "\t    if (ArrayUtil.isEmpty(ids)) {;"
        writer.writeLine "\t        return errorResult();"
        writer.writeLine "\t    }"
        writer.writeLine "\t    Arrays.stream(ids)"
        writer.writeLine "\t      .forEach(id -> {"
        writer.writeLine "\t      ${entityName} entity = new ${entityName}().set${entityName}Id(id);"
        writer.writeLine "\t      entity.setIsDel(DelFlag.DELETED.getCode());"
        writer.writeLine "\t      entity.setUpdateTime(DateUtils.getNowDate());"
        writer.writeLine "\t      ${service}.customSaveOrUpdate(entity,id);"
        writer.writeLine "\t    });"
        writer.writeLine "\t    return successResult();"
        writer.writeLine "\t}"


        writer.writeLine "}"
    }

}

class Utils {

    /**
     * 提示框
     * @param message
     * @return
     */
    static def dialog(message) {
        JOptionPane.showMessageDialog(null, message, "\u6807\u9898", JOptionPane.PLAIN_MESSAGE)
    }


    /**
     * 反射获取主键列名，
     * @param table
     * @return 若没有返回null
     */
    static def getPK(table) {
        def method = table.getClass().getMethod("getText")
        method.setAccessible(true)
        def text = method.invoke(table).toString()
        def reg = /(?<=\s{4,})\b[^\s]+\b(?!=.+\n\s+PRIMARY KEY,)/
        firstMatched(text, reg, null)
    }

    def static javaName(str, capitalize) {
        def s = str.split(/(?<=[^\p{IsLetter}])/).collect { Case.LOWER.apply(it).capitalize() }
            .join("").replaceAll(/[^\p{javaJavaIdentifierPart}]/, "_").replaceAll(/_/, "")
        capitalize || s.length() == 1 ? s : s.substring(0, 1).toLowerCase() + s[1..-1]
    }

    /**
     *  转换为大写驼峰
     * @param content
     * @return
     */
    static def toUpperCamelCase(content) {
        content.toString()
            .split(/_/)
            .toList()
            .stream()
            .filter { s -> s.length() > 0 }
            .map { s -> s.replaceFirst("^.", s.substring(0, 1).toUpperCase()) }
            .collect(Collectors.joining())
    }

    /**
     *  转换为驼峰
     * @param content
     * @return
     */
    static def toCamelCase(content) {
        content = content.toString()
        toUpperCamelCase(content).replaceFirst(/^./, content.substring(0, 1).toLowerCase())
    }

    static def getLowerEntityName(entityName){
        entityName = entityName.toString()
        def fc = entityName.substring(0, 1).toLowerCase()
        return fc + entityName.substring(1, entityName.length())
    }

    /**
     * 寻找第一个匹配的值
     * @param content 匹配内容
     * @param reg 正则
     * @param defaultValue 默认值
     * @return 根据正则匹配，能匹配就返回匹配的值，不能则匹配默认值
     */
    static def firstMatched(content, reg, defaultValue) {
        if (null == content) {
            return defaultValue
        }
        def m = content =~ reg
        if (m.find()) {
            return m.group()
        }
        return defaultValue
    }

    static def localDateTimeStr() {
        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
    }

    static def localDateTime() {
        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
    }

    static def createFile(filePath, fileName) {
        def file = new File(filePath)

        if (!file.exists()) {
            file.mkdir()
        }

        file = new File(filePath + "/" + fileName)
        if (!file.exists()) {
            file.createNewFile()
        }
        return file
    }

    static def getDefaultValIfCurrentValIsBlank(currentVal, defaultVal) {
        if (null == currentVal || currentVal.isEmpty()) {
            return defaultVal
        }
        return currentVal
    }
}

class Debug {

    // 反射获取所有方法
    static def console(col) {
        def clazz = col.getClass()
        def desktop = "D://"
        def fileName = "console${col.getName()}.txt"
        def path = desktop + fileName

        def file = new File(path)
        if (!file.exists()) {
            file.createNewFile()
        }

        file.withWriter("utf8") { writer ->
            writer.writeLine "base properties:"
            writer.writeLine "name\t${clazz.name}"
            writer.writeLine "simpleName\t${clazz.simpleName}"
            writer.writeLine ""

            writer.writeLine "public methods:"
            HashSet<Method> objMethodSet = new HashSet<Method>(new Object().getClass().methods as Collection<? extends Method>)
            HashSet<Method> colMethodSet = new HashSet<Method>(clazz.methods as Collection<? extends Method>)

            colMethodSet.stream()
                .filter { m -> !objMethodSet.contains(m) && m.name.matches("^(get|is).+") && m.parameterCount == 0 }
                .sorted { m1, m2 -> (m1.name <=> m2.name) }
                .forEach { m ->
                    m.setAccessible(true)
                    writer.writeLine "method name:\t${m.name}"
                    writer.writeLine "invoke result:\t${m.invoke(col)}"
                    writer.writeLine ""
                }

            writer.writeLine "all methods:"
            Arrays.stream(clazz.declaredMethods)
                .sorted { m1, m2 -> (m1.name <=> m2.name) }
                .forEach {
                    writer.writeLine "#"
                    writer.writeLine "name\t${it.name}"
                    writer.writeLine "accessible\t${it.accessible.toString()}"
                    writer.writeLine "return\t${it.returnType.name}"
                    writer.writeLine "paramCount\t${it.parameterCount}"
                    writer.write "param\t"
                    it.parameterTypes.each { writer.write "${it.name}\t" }
                    writer.write "param\t"
                    it.parameters.each { writer.write "${it.name}\t" }

                    writer.writeLine ""
                }

            writer.writeLine ""
        }
    }

    // 测试获取的字段
    static def consoleFields(fields) {
        def desktop = "D://"
        def fileName = "console-fields.txt"
        def path = desktop + fileName

        def file = new File(path)
        if (!file.exists()) {
            file.createNewFile()
        }

        file.withWriter("utf8") { writer ->
            fields.each {
                it.each { k, v ->
                    writer.writeLine "${k}:\t${v}"
                }
                writer.writeLine "======================================"
            }
        }
    }
}
